package com.medistat.dialog.services;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import com.medistat.dialog.ScheduleCell;
import com.medistat.dialog.ScheduleSelection;
import com.medistat.discretevalue.services.DiscreteValueConsts;
import com.medistat.discretevalue.services.DiscreteValueProvider;
import com.medistat.exceptions.ValidationException;
import com.medistat.i18n.TranslatorService;
import com.medistat.model.Department;
import com.medistat.model.Doctor;
import com.medistat.model.FreeDay;
import com.medistat.model.Holiday;
import com.medistat.model.Schedule;
import com.medistat.model.Setting;
import com.medistat.model.StandardPersistentObject;
import com.medistat.model.User;
import com.medistat.model.Work;
import com.medistat.model.enums.Days;
import com.medistat.model.enums.TypeOfFreeDay;
import com.medistat.model.enums.TypeOfWork;
import com.medistat.model.enums.WeekendDutiesInfoCreationPurpose;
import com.medistat.persistance.service.interfaces.PersistanceService;
import com.medistat.security.Decoder;
import com.medistat.util.DateUtils;
import com.medistat.util.ObjectFactory;

@Service("dialogService")
public class DialogServiceImpl implements DialogService {
	
	private PersistanceService persistanceService;
	private DiscreteValueProvider discreteValueProvider;
	private TranslatorService translatorService;
	private Creator createScheduleStrategy;
	private Creator loadScheduleStrategy;
	private DutiesProvider dutiesProvider;
	
	@Autowired
	public void setPersistanceService(PersistanceService persistanceService) {
		this.persistanceService = persistanceService;
	}
	
	@Autowired
	public void setDiscreteValueProvider(DiscreteValueProvider discreteValueProvider) {
		this.discreteValueProvider = discreteValueProvider;
	}
	
	@Autowired
	public void setCreateScheduleStrategy(@Qualifier("createScheduleStrategy")Creator scheduleCreator) {
		this.createScheduleStrategy = scheduleCreator;
	}
	
	@Autowired
	public void setLoadScheduleStrategy(@Qualifier("loadScheduleStrategy")Creator scheduleLoader) {
		this.loadScheduleStrategy = scheduleLoader;
	}
	
	@Autowired
	public void setTranslatorService(TranslatorService translatorService) {
		this.translatorService = translatorService;
	}

	@Autowired
	public void setDutiesProvider(DutiesProvider dutiesProvider) {
		this.dutiesProvider = dutiesProvider;
	}

	public PersistanceService getPersistanceService() {
		return persistanceService;
	}
	
	public DiscreteValueProvider getDiscreteValueProvider() {
		return discreteValueProvider;
	}
	
	@Override
	public <T extends StandardPersistentObject> void deleteObject(T object) throws Exception {
		getPersistanceService().deleteObject(object);
	}

	@Override
	public void addDoctor(String name, String surname) throws Exception {
		name = name.trim();
		surname = surname.trim();
		validateAddingDoctor(name, surname);
		try {
			Doctor doctor = ObjectFactory.create(Doctor.class);
			doctor.setName(name);
			doctor.setSurname(surname);
			doctor.setActive(Boolean.TRUE);
			getPersistanceService().addDoctor(doctor, 1L);
			
			getPersistanceService().addWeekendDutiesInfo(WeekendDutiesInfoCreationPurpose.NEW_DOCTOR, WeekendDutiesInfoCreationPurpose.NEW_DOCTOR + " " + name + " " + surname, Calendar.getInstance());
			
			getDiscreteValueProvider().clearDiscreteValueSet(DiscreteValueConsts.DOCTORS_METHOD);
			getDiscreteValueProvider().clearDiscreteValueSet(DiscreteValueConsts.DOCTORS_SURNAME_METHOD);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public void updateDoctor(Doctor doctor) throws Exception {
		if(doctor==null) {
			throw new Exception(translatorService.translate("warnning.objectRequired.doctor"));
		}
		getPersistanceService().updateDoctor(doctor);
		getDiscreteValueProvider().clearDiscreteValueSet(DiscreteValueConsts.DOCTORS_METHOD);
		getDiscreteValueProvider().clearDiscreteValueSet(DiscreteValueConsts.DOCTORS_SURNAME_METHOD);
	}

	private void validateAddingDoctor(String name, String surname) throws Exception {
		if(name==null) {
			throw new Exception(translatorService.translate("warnning.fieldRequired.doctor.name"));
		}
		if(surname==null) {
			throw new Exception(translatorService.translate("warnning.fieldRequired.doctor.surname"));
		}
		Doctor loadedDoctor = getPersistanceService().findDoctor(name, surname);
		if(loadedDoctor!=null) {
			throw new Exception(translatorService.translate("warnning.doctor.objectAlreadyExists"));
		}
	}
	
	@Override
	public void removeDoctor(Doctor doctor) {
		try {
			getPersistanceService().deleteDoctor(doctor);
			getDiscreteValueProvider().clearDiscreteValueSet(DiscreteValueConsts.DOCTORS_METHOD);
			getDiscreteValueProvider().clearDiscreteValueSet(DiscreteValueConsts.DOCTORS_SURNAME_METHOD);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public Doctor getDoctor(Long doctorId) {
		Doctor doctor = null;
		try {
			doctor = getPersistanceService().getDoctor(doctorId);
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return doctor;
	}

	@Override
	public List<Holiday> getAllHolidays() {
		return getPersistanceService().getAll(Holiday.class);
	}
	
	@Override
	public List<Holiday> getHolidaysForYear(Integer year) {
		return getPersistanceService().getHolidaysForYear(year);
	}

	@Override
	public void addHoliday(Date date, String description) throws Exception {
		Holiday holiday = ObjectFactory.create(Holiday.class);
		holiday.setDate(DateUtils.convertDateToCalendar(date));
		holiday.setDescription(description);
		getPersistanceService().addHoliday(holiday);
	}
	
	@Override
	public void removeHoliday(Holiday holiday) {
		try {
			getPersistanceService().deleteHoliday(holiday);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public void updateUser(String username, String oldPassword, String password, String confirmedPassword) throws Exception {
		if(username==null || password==null || confirmedPassword==null
					|| username.isEmpty() || password.isEmpty() || confirmedPassword.isEmpty()) {
			throw new Exception(translatorService.translate("warnning.user.allfieldsAreRequired"));
		}
		if(!password.equals(confirmedPassword)) {
			throw new Exception(translatorService.translate(translatorService.translate("warnning.user.incorrect.oldPassword")));
		}
		String hashedPassword  = Decoder.getMD5_Base64(oldPassword);
		User user = getPersistanceService().getUser(username, hashedPassword);
		if(user==null) {
			throw new Exception("Old password is not correct");
		}
		String newHashedPassword  = Decoder.getMD5_Base64(password);
		user.setPassword(newHashedPassword);
		getPersistanceService().updateUser(user);
	}
	
	@Override
	public void addUser(String username, String password, String confirmedPassword) throws Exception {
		if(username==null || password==null || confirmedPassword==null
					|| username.isEmpty() || password.isEmpty() || confirmedPassword.isEmpty()) {
			throw new Exception(translatorService.translate("warnning.user.allfieldsAreRequired"));
		}
		if(!password.equals(confirmedPassword)) {
			throw new Exception("Password and confirmed password must match!");
		}
		User user = ObjectFactory.create(User.class);
		user.setUsername(username);
		String hashedPassword  = Decoder.getMD5_Base64(password);
		user.setPassword(hashedPassword);
		getPersistanceService().addUser(user, 1L);
	}

	@Override
	public User getUser(String username, String password) throws Exception {
		if(username==null || password==null) {
			throw new Exception(translatorService.translate("warnning.user.allfieldsAreRequired"));
		}
		String hashedPassword  = Decoder.getMD5_Base64(password);
		return getPersistanceService().getUser(username, hashedPassword);
	}

	@Override
	public void addSetting(Days selectedDay, Long selectedDoctor, TypeOfWork selectedWork, Boolean... weeks) throws Exception {
		int week = 1;
		settingValidation(selectedDay, selectedDoctor, selectedWork, weeks);
		for(Boolean booleanWeek : weeks) {
			if(booleanWeek) {
			Setting setting = ObjectFactory.create(Setting.class);
			setting.setDayInWeek(selectedDay);
			setting.setDoctorId(selectedDoctor);
			setting.setTypeOfWork(selectedWork);
			setting.setWeekInMonth(week);
			getPersistanceService().addSetting(setting, 1L);
			}
			week++;
		}
	}

	//Move to Validation handling class
	private void settingValidation(Days selectedDay, Long selectedDoctor, TypeOfWork selectedWork, Boolean... weeks) throws ValidationException {
		if(Days.THURSDAY.equals(selectedDay) && TypeOfWork.EREKTILNA_DISFUNKCIJA.equals(selectedWork)) {
			if(weeks[0]==true && (weeks[1]!=false || weeks[2]!=false || weeks[3]!=false)) {
				throw new ValidationException(translatorService.translate("warnning.validationException.setting.erektilnaDisfunkcija"));
			}
			Setting setting = getPersistanceService().findSetting(selectedDay, selectedDoctor, selectedWork, Integer.valueOf(1));
			if(setting!=null) {
				throw new ValidationException(translatorService.translate("warnning.setting.objectAlreadyExists"));
			}
		}
	}

	@Override
	public void deleteSetting(Setting setting) throws Exception {
		getPersistanceService().deleteSetting(setting);
	}

	private void addFreeDay(TypeOfFreeDay selectedOutOfWork, Long selectedDoctor, Calendar dateFrom, Calendar dateTo) throws Exception {	
		if(selectedOutOfWork==null || selectedDoctor==null || dateFrom==null || dateTo==null) {
			throw new Exception(translatorService.translate("warnning.freeday.allfieldsAreRequired"));
		}
		FreeDay freeDay = ObjectFactory.create(FreeDay.class);
		freeDay.setDateFrom(dateFrom);
		freeDay.setDateTo(dateTo);
		freeDay.setOutOfWork(selectedOutOfWork);

		getPersistanceService().addFreeDay(freeDay, selectedDoctor);		
	}
	
	@Override
	public void addFreeDays(TypeOfFreeDay selectedOutOfWork, Long selectedDoctor, Calendar dateFrom, Calendar dateTo) throws Exception {
		if(dateFrom.after(dateTo)) {
			throw new Exception(translatorService.translate("warnning.freeday.dateFrom.dateTo.shouldBeBefore"));
		}
		addFreeDay(selectedOutOfWork, selectedDoctor, dateFrom, dateTo);
	}
	
	@Override
	public List<FreeDay> getFreeDays(Calendar dateFrom, Calendar dateUntil) throws Exception {
		if(dateFrom==null || dateUntil==null) {
			throw new Exception("warnning.freeday.dateFrom.dateTo.shouldNotBeEmpty");
		}
		return getPersistanceService().getFreeDays(dateFrom, dateUntil);
	}
	
	@Override
	public <T extends StandardPersistentObject> List<T> getAll(Class<T> clazz) {
		return getPersistanceService().getAll(clazz);
	}

	@Override
	public <T extends StandardPersistentObject> void saveAll(List<T> objects) throws Exception {
		getPersistanceService().saveAll(objects);
	}

	@Override
	public void saveSchedules(List<com.medistat.dialog.Schedule> schedules, Department department) throws Exception {
		List<Schedule> result = prepareSchedulesForSave(schedules, department);
		saveAll(result);
	}

	private List<Schedule> prepareSchedulesForSave(List<com.medistat.dialog.Schedule> schedules, Department department) throws Exception {
		List<Schedule> result = new ArrayList<Schedule>(); 
		for(com.medistat.dialog.Schedule schedule : schedules) {
			Schedule scheduleModelObject = ObjectFactory.create(Schedule.class);
			scheduleModelObject.setWeek(schedule.getWeek());
			scheduleModelObject.setYear(schedule.getYear());
			scheduleModelObject.setDepartment(department);
			for(ScheduleSelection scheduleSelection : schedule.getScheduleSelections()) {
				for(ScheduleCell scheduleCell : scheduleSelection.getScheduleCells()) {
					if(!scheduleCell.isActive()) {
						continue;
					}
					Work workModelObject = ObjectFactory.create(Work.class);
					workModelObject.setTypeOfWork(scheduleSelection.getTypeOfWork());
					Doctor doctor = scheduleCell.getDoctor();
					//if doctor has been manually changed after schedule generate on gui.
					if((doctor==null && scheduleCell.getDoctorId()!= null) || (doctor!=null && !doctor.getId().equals(scheduleCell.getDoctorId()))) {
						doctor = getPersistanceService().getDoctor(scheduleCell.getDoctorId());
					}
					workModelObject.setDoctor(doctor);
					workModelObject.setDate(scheduleCell.getDate());
					workModelObject.setDay(scheduleCell.getDay());
					workModelObject.setSchedule(scheduleModelObject);
					scheduleModelObject.getListOfWorks().add(workModelObject);
					
					//updating WeekendDutiesInfo object
					if(scheduleSelection.getTypeOfWork().equals(TypeOfWork.DUTY) && DateUtils.isWeekend(scheduleCell.getDay()) && doctor!=null) {
						getPersistanceService().updateWeekendDutiesInfo(doctor);
					}
				}
			}
			result.add(scheduleModelObject);
		}
		return result;
	}

	@Override
	public List<com.medistat.dialog.Schedule> generateSchedule(Calendar date) throws Exception {
		
		if(date==null) {
			throw new Exception(translatorService.translate("generateSchedule.warninng.dateIsMissing"));
		}
		
		//////////////////
		//validation til we dont get paid. Remove when paid
		Integer year = date.get(Calendar.YEAR);
		if(!year.equals(Integer.valueOf(2012))) {
			throw new Exception(translatorService.translate("licenseExpired.warning"));
		}
		//////////////////
		
		//adding of date for 1 is neccessery because month can be finished with saturday - in that case sunday (first in month) is also created.
		Calendar clone = (Calendar) date.clone();
		clone.add(Calendar.DATE, 1);
		if(checkIfWorkAlreadyExistFor(clone)) {
			throw new Exception(translatorService.translate("warnning.schedule.alreadyExists"));
		} 
		return createScheduleStrategy.createSchedule(date);
	}
	
	@Override
	public List<com.medistat.dialog.Schedule> loadSchedule(Calendar date) throws Exception {
		if(date==null) {
			throw new Exception(translatorService.translate("loadSchedule.warninng.dateIsMissing"));
		}
		//adding of date for 1 is neccessery because month can be finished with saturday - in that case sunday (first in month) is also created.
		Calendar clone = (Calendar) date.clone();
		clone.add(Calendar.DATE, 1);
		if(!checkIfWorkAlreadyExistFor(clone)) {
			throw new Exception(translatorService.translate("warnning.schedule.notExists"));
		}
		return loadScheduleStrategy.createSchedule(date);
	}

	private boolean checkIfWorkAlreadyExistFor(Calendar date) {		
		Work work = getPersistanceService().getWorkFor(date);
		if(work==null) {
			return false;
		}
		return true;
	}

	@Override
	public Map<String, Integer> getAllDutiesForAllDoctorsInYear(Integer year) throws Exception {
		return dutiesProvider.getAllDutiesForAllDoctorsInYear(year);
	}
	
	@Override
	public Map<String, Integer> getAllDutiesForAllDoctorsInMonth(Integer year, Integer month) throws Exception {
		return dutiesProvider.getAllDutiesForAllDoctorsInMonth(year, month);
	}
}
